package org.truenewx.tnxjee.webmvc.response.exception.resolver;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.util.CollectionUtil;
import org.truenewx.tnxjee.core.util.ExceptionUtil;
import org.truenewx.tnxjee.service.exception.*;
import org.truenewx.tnxjee.webmvc.method.support.HandlerMethodMapping;
import org.truenewx.tnxjee.webmvc.response.exception.message.ResolvableExceptionMessageSaver;

import javax.annotation.Nullable;
import java.util.Set;

/**
 * 可解决异常解决器
 */
public abstract class ResolvableExceptionResolver extends AbstractHandlerExceptionResolver {

    @Autowired
    protected HandlerMethodMapping handlerMethodMapping;
    @Autowired
    protected ResolvableExceptionMessageSaver messageSaver;

    public static boolean supports(Exception ex) {
        ex = convert(ex, false);
        return ex instanceof ConstraintViolationException || ex instanceof ResolvableException;
    }

    public static Exception convert(Exception ex, boolean convertToFormatException) {
        if (ex instanceof TransactionSystemException) {
            Throwable t = ExceptionUtil.traverseCause(ex, cause -> cause instanceof ConstraintViolationException
                    || cause instanceof ResolvableException);
            if (t instanceof Exception) {
                ex = (Exception) t;
            }
        }
        if (convertToFormatException && ex instanceof ConstraintViolationException cve) {
            Set<ConstraintViolation<?>> violations = cve.getConstraintViolations();
            if (violations != null && !violations.isEmpty()) {
                if (violations.size() == 1) {
                    ConstraintViolation<?> violation = CollectionUtil.getFirst(violations, null);
                    ex = buildFormatException(violation);
                } else {
                    MultiException me = new MultiException();
                    for (ConstraintViolation<?> violation : violations) {
                        me.add(buildFormatException(violation));
                    }
                    ex = me;
                }
            }
        }
        return ex;
    }

    @Override
    protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) {
        ex = convert(ex, true);
        if (ex instanceof ResolvableException re && supports(request, handler)) {
            this.messageSaver.saveMessage(request, response, handler, re);
            return getResult(request, response, handler, re);
        }
        return null;
    }

    private static FormatException buildFormatException(ConstraintViolation<?> violation) {
        String code = violation.getMessageTemplate().replace("{", "").replace("}", "");
        String property = violation.getPropertyPath().toString();
        return new FormatException(code, violation.getRootBeanClass(), property);
    }

    protected abstract boolean supports(HttpServletRequest request, @Nullable Object handler);

    protected abstract ModelAndView getResult(HttpServletRequest request,
            HttpServletResponse response, @Nullable Object handler, ResolvableException re);

    @Override
    protected String buildLogMessage(Exception ex, HttpServletRequest request) {
        if (ex instanceof SingleException) {
            return buildLogMessage((SingleException) ex);
        } else if (ex instanceof MultiException me) {
            StringBuilder message = new StringBuilder();
            me.forEach(se -> {
                String singleMessage = buildLogMessage(se);
                if (StringUtils.isNotBlank(singleMessage)) {
                    message.append(singleMessage).append(Strings.ENTER);
                }
            });
            return message.toString().trim();
        }
        return super.buildLogMessage(ex, request);
    }

    private String buildLogMessage(SingleException se) {
        if (se instanceof BusinessException) {
            return buildLogMessage((BusinessException) se);
        } else if (se instanceof FormatException) {
            return buildLogMessage((FormatException) se);
        }
        return null;
    }

    private String buildLogMessage(BusinessException be) {
        StringBuilder message = new StringBuilder("====== ").append(be.getCode());
        String args = StringUtils.join(be.getArgs(), Strings.COMMA);
        if (StringUtils.isNotBlank(args)) {
            message.append(Strings.COLON).append(args);
        }
        if (be.isBoundProperty()) {
            message.append(Strings.LEFT_BRACKET).append(be.getProperty()).append(Strings.RIGHT_BRACKET);
        }
        message.append(" ======");
        return message.toString();
    }

    private String buildLogMessage(FormatException fe) {
        return "====== " + fe.getCode() + Strings.LEFT_BRACKET + fe.getModelClass().getName() + Strings.DOT + fe
                .getProperty() + Strings.RIGHT_BRACKET + " ======";
    }

}
